import OSS from 'ali-oss';

export interface OssCredentials {
  accessKeyId: string;
  accessKeySecret: string;
  stsToken?: string;
  bucket: string;
  region: string;
  baseDir?: string;
  secure?: boolean;
  authorizationV4?: boolean;
}

export interface UploadOptions {
  progress?: (percent: number, cpt: any, res: any) => void;
  parallel?: number;
  partSize?: number;
  forbidOverwrite?: boolean;
}

export interface UploadResult {
  name: string;
  url: string;
  res: any;
}

export interface StsCredentialsResponse {
  accessKeyId: string;
  accessKeySecret: string;
  securityToken: string;
  regionId: string;
  bucketName: string;
  baseDir: string;
}

export class OssUploadClient {
  private client: OSS;
  private options: OssCredentials;

  /**
   * Create a new OSS client
   * @param options - Configuration options
   */
  constructor(options: OssCredentials) {
    this.options = {
      secure: true,
      authorizationV4: false,
      baseDir: '',
      ...options
    };

    this.client = new OSS({
      region: this.options.region,
      accessKeyId: this.options.accessKeyId,
      accessKeySecret: this.options.accessKeySecret,
      stsToken: this.options.stsToken,
      bucket: this.options.bucket,
      secure: this.options.secure,
      authorizationV4: this.options.authorizationV4
    });
  }

  /**
   * Get MIME type based on file extension
   * @param fileName - Name of the file
   * @returns MIME type
   */
  private getFileMimeType(fileName: string): string {
    const extension = fileName.toLowerCase().split('.').pop() || '';
    const mimeTypes: Record<string, string> = {
      svg: 'image/svg+xml',
      png: 'image/png',
      jpg: 'image/jpeg',
      jpeg: 'image/jpeg',
      gif: 'image/gif',
    };
    return mimeTypes[extension] || '';
  }

  /**
   * Upload a file to OSS with multipart upload support
   * @param file - File or Blob object to upload
   * @param options - Upload options
   * @returns Upload result
   */
  async uploadFile(file: File & { originFileObj?: File }, options: UploadOptions = {}): Promise<UploadResult> {
    try {
      const fileNameSplit = file.name.split('.');
      const suffix = fileNameSplit.pop();
      const name = `${this.options.baseDir}/${fileNameSplit.join('')}${Date.now()}.${suffix}`;

      // Encode filename to handle special characters
      const encodedFileName = encodeURIComponent(file.name).replace(/['()]/g, escape);
      const mimeType = this.getFileMimeType(file.name);

      const headers = {
        // Specify cache behavior when the Object is downloaded.
        'Cache-Control': 'no-cache',
        // Specify the name of the Object when downloaded, using UTF-8 encoding to avoid issues with non-ASCII characters
        'Content-Disposition': `attachment; filename="${encodedFileName}"`,
        // Specify expiration time in milliseconds
        'Expires': '1000',
        // Specify storage type of the Object
        'x-oss-storage-class': 'Standard',
        // Specify Object tags, multiple tags can be set simultaneously
        'x-oss-tagging': 'Tag1=1&Tag2=2',
        // Specify whether to overwrite the same Object during multipart upload initialization
        'x-oss-forbid-overwrite': options.forbidOverwrite ? 'true' : 'false',
        'x-oss-object-acl': 'public-read', // File permission settings
        'Access-Control-Expose-Headers': 'ETag,x-oss-request-id',
        ...(mimeType && { 'Content-Type': mimeType }),
      };

      const uploadOptions = {
        // Progress callback
        progress: options.progress,
        // Set the number of concurrent parts
        parallel: options.parallel || 4,
        // Set the part size. Default is 1 MB, minimum is 100 KB, maximum is 5 GB
        partSize: options.partSize || 1024 * 1024,
        headers,
        // Custom metadata
        meta: { year: new Date().getFullYear().toString(), people: 'sdk-user' },
      };

      // Perform multipart upload
      const res = await this.client.multipartUpload(
        name, 
        file.originFileObj ?? file, 
        uploadOptions
      );

      return {
        name: name,
        url: `https://${this.options.bucket}.${this.options.region}.aliyuncs.com/${name}`,
        res: res
      };
    } catch (err: any) {
      // Handle request header errors caused by filename encoding
      if (err.name === 'TypeError' && err.message && err.message.includes('setRequestHeader')) {
        throw new Error('文件名包含特殊字符，请重命名后重新上传'); // Filename contains special characters, please rename and upload again
      } else {
        throw new Error(`上传失败: ${err.message}`); // Upload failed
      }
    }
  }

  /**
   * Save a file from URL to local download
   * @param url - URL of the file to download
   * @param fileName - Name to save the file as
   */
  async saveFile(url: string, fileName: string): Promise<void> {
    // This implementation is meant for browser environments
    if (typeof window === 'undefined') {
      throw new Error('saveFile is intended for browser environments');
    }

    try {
      const res = await fetch(url);
      const blob = await res.blob();
      const urlNew = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = urlNew;
      a.download = fileName;
      document.body.appendChild(a);
      a.click();
      a.remove();
      URL.revokeObjectURL(urlNew);
    } catch (err: any) {
      throw new Error(`Failed to save file: ${err.message}`);
    }
  }

  /**
   * Delete a file from OSS
   * @param objectName - Object name in OSS
   * @returns Deletion result
   */
  async deleteFile(objectName: string): Promise<boolean> {
    try {
      await this.client.delete(objectName);
      return true;
    } catch (err) {
      console.error('Delete file error:', err);
      return false;
    }
  }
  
  /**
   * Get STS credentials and update the client
   * @param getCredentials - Function that returns a promise resolving to STS credentials
   */
  async updateCredentials(getCredentials: () => Promise<StsCredentialsResponse>): Promise<void> {
    try {
      const stsCredentials = await getCredentials();
      const newOptions = {
        region: `oss-${stsCredentials.regionId}`,
        accessKeyId: stsCredentials.accessKeyId,
        accessKeySecret: stsCredentials.accessKeySecret,
        stsToken: stsCredentials.securityToken,
        bucket: stsCredentials.bucketName,
        secure: true,
        authorizationV4: false,
        baseDir: stsCredentials.baseDir
      };
      
      this.client = new OSS(newOptions);
      this.options = {
        ...this.options,
        ...newOptions
      };
    } catch (err: any) {
      throw new Error(`Failed to update credentials: ${err.message}`);
    }
  }
  
  /**
   * Create an instance with automatic credential fetching
   * @param getCredentials - Function that returns a promise resolving to STS credentials
   * @returns New OssUploadClient instance
   */
  static async createWithCredentials(getCredentials: () => Promise<StsCredentialsResponse>): Promise<OssUploadClient> {
    try {
      const stsCredentials = await getCredentials();
      const options = {
        region: `oss-${stsCredentials.regionId}`,
        accessKeyId: stsCredentials.accessKeyId,
        accessKeySecret: stsCredentials.accessKeySecret,
        stsToken: stsCredentials.securityToken,
        bucket: stsCredentials.bucketName,
        secure: true,
        authorizationV4: false,
        baseDir: stsCredentials.baseDir
      };
      
      return new OssUploadClient(options);
    } catch (err: any) {
      throw new Error(`Failed to create client with credentials: ${err.message}`);
    }
  }
}

export function getFileMimeType(fileName: string): string {
  const extension = fileName.toLowerCase().split('.').pop() || '';
  const mimeTypes: Record<string, string> = {
    svg: 'image/svg+xml',
    png: 'image/png',
    jpg: 'image/jpeg',
    jpeg: 'image/jpeg',
    gif: 'image/gif',
  };
  return mimeTypes[extension] || '';
}

export async function saveFile(url: string, fileName: string): Promise<void> {
  // This implementation is meant for browser environments
  if (typeof window === 'undefined') {
    throw new Error('saveFile is intended for browser environments');
  }

  try {
    const res = await fetch(url);
    const blob = await res.blob();
    const urlNew = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = urlNew;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    a.remove();
    URL.revokeObjectURL(urlNew);
  } catch (err: any) {
    throw new Error(`Failed to save file: ${err.message}`);
  }
}

export default OssUploadClient;